package com.rightrule.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.common.util.Md5Utils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Lists;
import com.rightrule.entity.DecisionFieldEntity;
import com.rightrule.entity.DecisionFieldInterfaceEntity;
import com.rightrule.rule.constans.*;
import com.rightrule.rule.script.ScriptFactory;
import com.rightrule.rule.dto.CommonFieldComputeDTO;
import com.rightrule.rule.dto.DecisionFieldDTO;
import com.rightrule.rule.dto.FieldComputResult;
import com.rightrule.rule.dto.FieldComputeTask;
import com.rightrule.service.DecisionFieldService;
import com.rightrule.service.FieldComputeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
public class FieldComputeServiceImpl implements FieldComputeService {

    @Resource(name = "fieldTaskExecutor")
    private ThreadPoolTaskExecutor threadPool;

    @Autowired
    private DecisionFieldService decisionFieldService;

    // todo 后续完善
    private Map<Long, DecisionFieldInterfaceEntity> fieldInterfaceCacheList;

    /**
     * 变量拉取 todo
     *
     * @param commonFieldComputeDTO
     * @return
     */
    @Override
    public JSONObject compute(CommonFieldComputeDTO commonFieldComputeDTO) {

        long start = System.nanoTime();
        JSONObject result = new JSONObject();
        log.info("common compute, dto ={},versionId = {}", commonFieldComputeDTO, commonFieldComputeDTO.getStrategyVersionId());

        if (CollectionUtils.isEmpty(commonFieldComputeDTO.getFieldNameList())) {
            log.error("common compute, fields list should not be empty");
            return result;
        }

        // 构造拉取任务列表,当前默认拉取版本下所有的变量
        List<FieldComputeTask> fieldComputeTasks = mergeFieldReqTask(commonFieldComputeDTO.getFieldNameList(), null, commonFieldComputeDTO);
        log.info("merge field tasks for compute,size ={},tasks={}", fieldComputeTasks.size(), fieldComputeTasks);

        // 打印线程池监控，观察当前资源的情况

        ArrayList<FieldComputResult> fieldComputResultList = Lists.newArrayList();
        Lock lock = new ReentrantLock();

        try {
            log.info("all off begin..");
            // todo 下面的拉取逻辑完善后开始构建拉取
            CompletableFuture<FieldComputResult>[] futures = null;

        } catch (Exception e) {
            log.error("Async compute field error,e={}", e);
            throw new RuntimeException(e);
        }

        // 拉取的变量有缺失，则后续决策会有误差,这里先把日志打印出来
        if (commonFieldComputeDTO.getFieldNameList().size() > result.size()) {
            log.error("common compute,lack return field,origin size = {} ,actual size={},originFields={},actualFields={}",
                    commonFieldComputeDTO.getFieldNameList().size(), result.size(), commonFieldComputeDTO.getFieldNameList(), result);
        }

        long cost = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        // todo 设置链路追踪逻辑

        return result;
    }

    /**
     * 构造拉取任务列表 todo
     *
     * @param fieldNameList
     * @param fieldVersion
     * @param commonFieldComputeDTO
     * @return
     */
    private List<FieldComputeTask> mergeFieldReqTask(List<String> fieldNameList, String fieldVersion, CommonFieldComputeDTO commonFieldComputeDTO) {

        // 请求任务列表
        HashMap<String, FieldComputeTask> taskTagInfoMap = new HashMap<>();

        for (String fieldName : fieldNameList) {
            DecisionFieldDTO fieldDTO;

            // 在线变量请求
            if (StringUtils.isEmpty(fieldVersion)) {
                // todo 逻辑待实现
//                fieldDTO = fieldOnlineCacheLogic.getFieldOnLineCache(fieldName);
                fieldDTO = null;
                if (Objects.isNull(fieldDTO)) {
                    // 如果本地没有当前在线的变量版本则决策逻辑会走默认变量，当前需要打日志，或者监控告警通知，可能会影响业务逻辑
                    log.error("no hit field online cache,fieldName={}", fieldName);
                    continue;
                }
            }
            // 变量版本测试请求
            else {
                // todo 逻辑待实现
//                fieldDTO = fieldVersionCacheLogic.getFieldVersionCache(fieldName,fieldVersion);
                fieldDTO = null;
                if (Objects.isNull(fieldDTO)) {
                    log.error("no hit field online cache,fieldName={},version = {}", fieldName, fieldVersion);

                    // 从数据库中拉取对应的版本
                    QueryWrapper<DecisionFieldEntity> fieldEntityQueryWrapper = new QueryWrapper<>();
                    fieldEntityQueryWrapper.eq("field_name", fieldName);
                    fieldEntityQueryWrapper.eq("version_id", fieldVersion);
                    DecisionFieldEntity fieldEntity = decisionFieldService.getOne(fieldEntityQueryWrapper);
                    if (Objects.isNull(fieldEntity)) {
                        log.error("fetch null field from remote,fieldName = {},fieldVersion={}", fieldName, fieldVersion);
                        continue;
                    }

                    // 如果是脚本需要编译好
                    if (FieldSourceEnum.SCRIPT_EXECUTE.getCode().equals(fieldEntity.getSource())
                            && !StringUtils.isEmpty(fieldEntity.getScriptData())
                    ) {
                        Class scriptClass = ScriptFactory.getScriptClass(fieldEntity.getScriptData(), fieldEntity.getFieldName(), fieldEntity.getFieldLogic());
                        BeanUtils.copyProperties(fieldEntity, fieldDTO);
                        fieldDTO.setGroovyClass(scriptClass);
                    }

                    // todo 存入缓存中，后续直接使用
                    log.info("fetch field from remote,put into field version cache,fieldName={},fieldVersion = {}", fieldName, fieldVersion);
                }
            }

            // 合并变量请求：获取任务标识（如果两个字段生成的任务标识相同，则可以合并成一个拉取任务）
            String taskTag = extractComputeTaskTag(fieldDTO, commonFieldComputeDTO);
            log.info("extract tag success,commonFieldComputeDTO={},fieldDTO={},tag={}", commonFieldComputeDTO, fieldDTO, taskTag);

            // 如果拉取任务合集里面没有包含当前拉取任务
            if (!taskTagInfoMap.containsKey(taskTag)) {
                FieldComputeTask fieldComputeTask = new FieldComputeTask();

                // 设置请求字段相关属性
                addComputeTaskData(fieldComputeTask, fieldName, fieldDTO);

                // 设置请求参数
                fieldComputeTask.setReqParams(fieldDTO.getParams());

                // 设置对外请求接口
                if (FieldSourceEnum.RPC_PULL.getCode().equals(fieldDTO.getSource()) && !loadFieldInterface(fieldDTO, fieldComputeTask)) {
                    continue;
                } else if (FieldSourceEnum.ODPS_OFFLINE_PULL.getCode().equals(fieldDTO.getSource()) && !StringUtils.isEmpty(fieldDTO.getDbSource())) {
                    fieldComputeTask.setDbSource(fieldDTO.getDbSource());
                }

                // 设置请求基础信息
                fieldComputeTask.setIpId(commonFieldComputeDTO.getIpId());
                fieldComputeTask.setBusinessId(commonFieldComputeDTO.getBusinessId());
                fieldComputeTask.setSource(FieldSourceEnum.of(fieldDTO.getSource()).get());
                fieldComputeTask.setTaskTag(taskTag);

                // 设置透传参数
                fieldComputeTask.setSessionData(commonFieldComputeDTO.getSessionData());

                // groovy变量则要设置执行脚本
                if (FieldSourceEnum.SCRIPT_EXECUTE.getCode().equals(fieldDTO.getSource())) {
                    fieldComputeTask.setGroovyClass(fieldDTO.getGroovyClass());
                }

                // 加入请求列表
                taskTagInfoMap.put(taskTag, fieldComputeTask);
            }
            // 可合并任务
            else {
                FieldComputeTask fieldComputeTask = taskTagInfoMap.get(taskTag);

                // 设置请求参数相关属性
                addComputeTaskData(fieldComputeTask, fieldName, fieldDTO);
            }
        }

        return new ArrayList<>(taskTagInfoMap.values());
    }


    /**
     * @param fieldDTO
     * @param fieldComputeTask
     * @return
     */
    private boolean loadFieldInterface(DecisionFieldDTO fieldDTO, FieldComputeTask fieldComputeTask) {

        // todo 缓存逻辑待完善
        DecisionFieldInterfaceEntity interfaceEntity = fieldInterfaceCacheList.get(fieldDTO.getVersionId());
        if (Objects.nonNull(interfaceEntity)) {
            fieldComputeTask.setInterfaceId(interfaceEntity.getId());
            fieldComputeTask.setInterfaceName(interfaceEntity.getInterfaceName());
            fieldComputeTask.setInterfaceMethodName(interfaceEntity.getMethodName());
            fieldComputeTask.setInterfaceVersion(interfaceEntity.getVersion());
            return true;
        }

        log.info("no hit field interface cache,fieldName={},interfaceId={},fieldDTO={}", fieldDTO.getFieldName(), fieldDTO.getVersionId(), fieldDTO);

        // todo FieldInterfaceLoadDTO

        return false;
    }

    /**
     * 设置请求字段相关属性
     *
     * @param fieldComputeTask
     * @param fieldName
     * @param fieldDTO
     */
    private void addComputeTaskData(FieldComputeTask fieldComputeTask, String fieldName, DecisionFieldDTO fieldDTO) {
        // 设置追踪器
//        fieldComputeTask.getTracerMas().put(fieldName,fieldTracerLogic.pre(fieldDTO));

        // 增加请求变量
        fieldComputeTask.getRequestFieldList().add(fieldName);

        // 请求变量返回的结果类型
        fieldComputeTask.getFieldDataTypeMap().put(fieldName, fieldDTO.getReturnType());

        // 变量校验信息
        fieldComputeTask.getFieldValidateTypeMap().put(fieldName, fieldDTO.getValidationType());
        fieldComputeTask.getFieldValidateScriptMap().put(fieldName, fieldDTO.getValidationScript());

        // 请求变量返回默认值
        if (FallbackMethodEnum.USE_DEFAULT.getCode().equals(fieldDTO.getFallbackMethod())) {

            if (FieldReturnTypeEnum.INT_RETURN.getCode().equals(fieldDTO.getReturnType())) {
                fieldComputeTask.getExceptDataValueMap().put(fieldName, Integer.valueOf(fieldDTO.getDefaultValue()));
            } else if (FieldReturnTypeEnum.DOUBLE_RETURN.getCode().equals(fieldDTO.getReturnType())) {
                fieldComputeTask.getExceptDataValueMap().put(fieldName, Double.valueOf(fieldDTO.getDefaultValue()));
            } else {
                fieldComputeTask.getExceptDataValueMap().put(fieldName, fieldDTO.getDefaultValue());
            }
        }

        // 请求变量在外部返回Bean中对应的成员变量
        fieldComputeTask.getValueKeyMap().put(fieldName, fieldDTO.getValueKey());
        log.info("add compute task data success, fieldName = {},fieldVersionId={},fieldComputeTask={}", fieldName, fieldDTO.getVersionId(), fieldComputeTask);

    }

    /**
     * todo 提取拉取任务
     *
     * @param fieldDTO
     * @param commonFieldComputeDTO
     * @return
     */
    private String extractComputeTaskTag(DecisionFieldDTO fieldDTO, CommonFieldComputeDTO commonFieldComputeDTO) {

        if (FieldSourceEnum.RPC_PULL.getCode().equals(fieldDTO.getSource())) {
            return extractRpcTag(fieldDTO);
        } else if (FieldSourceEnum.ODPS_OFFLINE_PULL.getCode().equals(fieldDTO.getSource())) {
            return extractSqlTag(fieldDTO, commonFieldComputeDTO);
        } else if (FieldSourceEnum.SCRIPT_EXECUTE.getCode().equals(fieldDTO.getSource())) {
            return extractScriptTag(fieldDTO);
        }
        return defaultTag(fieldDTO);

    }

    /**
     * 组装默认入参
     *
     * @param fieldDTO
     * @return
     */
    private String defaultTag(DecisionFieldDTO fieldDTO) {
        return null;
    }

    /**
     * 组装脚本参数
     *
     * @param fieldDTO
     * @return
     */
    private String extractScriptTag(DecisionFieldDTO fieldDTO) {
        return FieldSourceEnum.RPC_PULL.name() + "AT" + Md5Utils.getMD5(fieldDTO.getScriptData(), "UTF-8") + "AT" + fieldDTO.getParams();
    }

    /**
     * 组装sql参数
     *
     * @param fieldDTO
     * @param commonFieldComputeDTO
     * @return
     */
    private String extractSqlTag(DecisionFieldDTO fieldDTO, CommonFieldComputeDTO commonFieldComputeDTO) {
        return null;
    }

    /**
     * 组装参数Rpc参数
     *
     * @param fieldDTO
     * @return
     */
    private String extractRpcTag(DecisionFieldDTO fieldDTO) {
        return FieldSourceEnum.RPC_PULL.name() + "AT" + fieldDTO.getInterfaceId().toString() + "AT" + fieldDTO.getParams();
    }


    /**
     * 异步处理字段的返回
     *
     * @param result
     * @param fieldComputResult
     * @param fieldComputResultList
     * @param lock
     * @param task
     */
    public void invokeComplete(JSONObject result, FieldComputResult fieldComputResult,
                               List<FieldComputResult> fieldComputResultList, Lock lock, FieldComputeTask task) {
        if (Objects.isNull(fieldComputResult)) {
            log.error("field compute result is null,task={}", task);
            return;
        }

        // 自动映射方法返回空时取默认值
        if (Objects.isNull(fieldComputResult) || fieldComputResult.isHasError()) {
            JSONObject jsonObject = new JSONObject();
            fieldComputResult.setResult(jsonObject);
        }

        // 计算后的结果值
        JSONObject computResult = fieldComputResult.getResult();

        // 获取变量拉取结果
        for (String requestFieldName : task.getRequestFieldList()) {
            Object resultValue = null;

            // 变量对应返回是有别名
            if (!task.getValueKeyMap().isEmpty() && task.getValueKeyMap().containsKey(requestFieldName)) {
                // 按别名去取结果值
                resultValue = computResult.get(task.getValueKeyMap().get(requestFieldName));
            }

            // 如果没有别名，则直接按照字段去取值
            else if (Objects.nonNull(computResult.get(requestFieldName))) {
                resultValue = computResult.get(requestFieldName);
            }
            // 如果没有对应数据返回，并且没有报错
            if (Objects.isNull(resultValue) && !fieldComputResult.isHasError() && task.getExceptDataValueMap().containsKey(requestFieldName)) {
                resultValue = task.getExceptDataValueMap().get(requestFieldName);

                // 默认是空字符串的话，需要转一下
                if (resultValue instanceof String && CommonConstant.DEFAULT_EMPTY_STR.equals(resultValue)) {
                    resultValue = "";
                }

                log.info("field get empty,fieldName ={},fillDefaultValue={}", requestFieldName, resultValue);
            }
            // 如果执行异常则取默认值
            else if (fieldComputResult.isHasError() && task.getExceptDataValueMap().containsKey(requestFieldName)) {

                result.put(DecisionResultKeyEnum.COMMON_REQUEST_STATUS.getCode(), CommonConstant.ERROR);
                result.getJSONArray(DecisionResultKeyEnum.ERROR_FIELD_LIST.getCode()).add(requestFieldName);
                log.error("field get error,fieldName = {},fillDefaultValue={}", requestFieldName, resultValue);

            } else if (fieldComputResult.isHasError() && !task.getExceptDataValueMap().containsKey(requestFieldName)) {

                result.put(DecisionResultKeyEnum.COMMON_REQUEST_STATUS.getCode(), CommonConstant.ERROR);
                result.getJSONArray(DecisionResultKeyEnum.ERROR_FIELD_LIST.getCode()).add(requestFieldName);
                log.error("field get error,fieldName = {},fillDefaultValue={}", requestFieldName, resultValue);

            }

            if (Objects.nonNull(resultValue)) {
                lock.lock();
                try {
                    result.put(requestFieldName, resultValue);
                    computResult.put(requestFieldName, resultValue);
                } finally {
                    lock.unlock();
                }
            }

            // 两快校验，数据类型和值校验，这边只返回校验的结果
            boolean validateResult = validateFieldValue(result, requestFieldName, task);

            // todo 待实现
//            fieldTraceLogic.post(task,fieldComputResult,validateResult);
        }

        fieldComputResultList.add(fieldComputResult);
    }

    /**
     * 字段值校验
     *
     * @param result
     * @param requestFieldName
     * @param task
     * @return
     */
    private boolean validateFieldValue(JSONObject result, String requestFieldName, FieldComputeTask task) {
        Object fieldValue = result.get(requestFieldName);
        Integer fieldValueType = task.getFieldDataTypeMap().get(requestFieldName);

        // 校验变量值的类型
        // 1，如果变量是int，但返回String类型数字,则会转成int
        // 2,如果变量是double，但是返回string类型的double,则会转换成double
        if (fieldValue != null && fieldValueType != null) {
            boolean invalidate = false;
            if (FieldReturnTypeEnum.INT_RETURN.getCode().equals(fieldValueType)) {
                if (!(fieldValue instanceof Integer) && !(fieldValue instanceof Long)) {
                    invalidate = true;
                    if (fieldValue instanceof String) {
                        try {
                            fieldValue = Integer.parseInt((String) fieldValue);
                            result.put(requestFieldName, fieldValue);
                            invalidate = false;
                        } catch (Exception e) {
                            log.error("字段返回值数据类型与配置不一致，字符串转int失败，fieldName = {},fieldType = {},value={}", requestFieldName, fieldValueType, fieldValue);
                        }
                    }
                }
            } else if (FieldReturnTypeEnum.VARCHAR_RETURN.getCode().equals(fieldValueType)) {
                if (this.isDate(fieldValue)) {
                    invalidate = true;
                }
            } else if (FieldReturnTypeEnum.DOUBLE_RETURN.getCode().equals(fieldValueType)) {
                if (!this.isDate(fieldValue)) {
                    invalidate = true;
                    if (fieldValue instanceof String) {
                        try {
                            fieldValue = Double.parseDouble((String) fieldValue);
                            result.put(requestFieldName, fieldValue);
                            invalidate = false;
                        } catch (Exception e) {
                            log.error("字段返回值数据类型与配置不一致，字符串转int失败，fieldName = {},fieldType = {},value={}", requestFieldName, fieldValueType, fieldValue);
                        }
                    }
                }
            }
            if (invalidate) {
                log.error("字段返回值数据类型与配置不一致，字符串转int失败，fieldName = {},fieldType = {},value={}", requestFieldName, fieldValueType, fieldValue);
            }
        }

        // 再做一下变量值的校验
        Integer validationType = task.getFieldDataTypeMap().get(requestFieldName);
        String validationScript = task.getFieldValidateScriptMap().get(requestFieldName);

        //
        if (StringUtils.isEmpty(validationScript) || Objects.isNull(validationType)) {
            return true;
        }

        // todo 规则决策
        boolean validateResult = true;
//      boolean validateResult = ruleDecisionService.decision()
//        if (!validateResult) {
//            log.error("field compute result validate failed,fieldName={},validationType= {},value={}", requestFieldName, validationType, fieldValue);
//        }

        return validateResult;
    }


    private boolean isDate(Object fieldValue) {
        return (fieldValue instanceof Double) || (fieldValue instanceof Float) ||
                (fieldValue instanceof Integer) || (fieldValue instanceof Long) ||
                (fieldValue instanceof BigDecimal);
    }

    /**
     * 异步调用单个任务
     *
     * @param task
     * @return
     */
    public FieldComputResult asyncInvoke(FieldComputeTask task) {
        FieldComputResult fieldComputResult = new FieldComputResult();
        fieldComputResult.setTaskTag(task.getTaskTag());

        log.info("async invoke begin ,tag={},fields={}", task.getTaskTag(), task.getRequestFieldList());
        long start = System.currentTimeMillis();
        try {
            // todo 明天实现
        } catch (Error er) {

        } catch (Exception e) {

        }
        long costTime = System.currentTimeMillis() - start;
        fieldComputResult.setCostTime(costTime);

        return fieldComputResult;
    }
}
